package com.liangsiwei.weblog.jwt.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Date;

@Component
public class JwtTokenHelper implements InitializingBean {
    /**
     * 签发人
     */
    @Value("${jwt.issuer:liangsiwei}")
    private String issuer;
    /**
     * 签名密钥（用于JWT的签名和验证
     */
    private Key key;

    /**
     * JWT解析器（用于验证和解析Token）
     */
    private JwtParser jwtParser;

    /**
     * Token 失效时间（分钟）
     */
    @Value("${jwt.tokenExpireTime:1440}")
    private Long tokenExpireTime;

    /**
     * 解码配置文件中配置的 Base 64 编码 key 为秘钥
     * @param base64Key
     */
    @Value("${jwt.secret}")
    public void setBase64Key(String base64Key) {
        /** 从配置文件中读取 Base64 编码的密钥字符串（${jwt.secret}），
         * 解码后生成用于 HMAC-SHA 算法的签名密钥 */
        key = Keys.hmacShaKeyFor(Base64.getDecoder().decode(base64Key));
    }

    /**
     * 初始化 JwtParser
     *
     * @throws Exception
     */
    public void afterPropertiesSet() throws Exception{
        // 考虑到不同服务器之间可能存在时钟偏移，setAllowedClockSkewSeconds 用于设置能够容忍的最大的时钟误差
        jwtParser = Jwts.parserBuilder()
                .requireIssuer(issuer)  // 要求Token的issuer必须与当前配置的issuer一致
                .setSigningKey(key)    // 设置签名密钥（用于验证Token的签名）
                .setAllowedClockSkewSeconds(10)  // 允许的时钟偏移（10秒，解决服务器时间不同步问题）
                .build();
    }

    /**
     * 生成 Token
     * @param username
     * @return
     */
    public String generateToken(String username){
        LocalDateTime now = LocalDateTime.now();  // 当前时间
        LocalDateTime expireTime = now.plusMinutes(tokenExpireTime);
        return Jwts.builder()
                .setSubject(username)  // 设置主题（subject，通常存储用户标识，如用户名）
                .setIssuer(issuer)     // 设置签发人（与解析时的requireIssuer对应）
                .setIssuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()))  // 签发时间
                .setExpiration(Date.from(expireTime.atZone(ZoneId.systemDefault()).toInstant())) // 过期时间
                .signWith(key)  // 用密钥签名（确保Token不被篡改）
                .compact();  // 生成最终的Token字符串
    }
    /**
     * 解析 Token  解析Token并验证其合法性，返回包含声明的Jws对象
     * @param token
     * @return
     */
    public Jws<Claims> parseToken(String token) {
        try {
            return jwtParser.parseClaimsJws(token);  // 解析并验证Token
            //jwtParser.parseClaimsJws(token)会自动执行所有验证（签名、签发人、过期时间、时钟偏移等）。

        } catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            // 签名错误、Token格式错误、不支持的Token类型、参数错误等
            throw new BadCredentialsException("Token 不可用", e);
        } catch (ExpiredJwtException e) {
            // Token已过期
            throw new CredentialsExpiredException("Token 失效", e);
        }
    }

    /**
     * 生成一个 Base64 的安全秘钥
     * @return
     */
    private static String generateBase64Key() {
        // 生成HS512算法的安全密钥（长度足够长，确保安全性）
        Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512);

        // 将密钥字节数组编码为Base64字符串（方便存储到配置文件）
        String base64Key = Base64.getEncoder().encodeToString(secretKey.getEncoded());

        return base64Key;
    }

    /**
     * 校验 Token 是否可用
     * @param token
     * @return
     */
    public void validateToken(String token) {
        jwtParser.parseClaimsJws(token);
    }

    /**
     * 解析 Token 获取用户名
     * @param token
     * @return
     */
    public String getUsernameByToken(String token) {
        try {
            Claims claims = jwtParser.parseClaimsJws(token).getBody();
            String username = claims.getSubject();
            return username;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    public static void main(String[] args) {
        String key = generateBase64Key();
        System.out.println("key: " + key);
    }

}
